{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# `Nuqleon.Time`\n",
        "\n",
        "Provides a set of abstractions over notions of time, including clocks and stopwatches."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Reference the library"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Option 1 - Use a local build\n",
        "\n",
        "If you have built the library locally, run the following cell to load the latest build."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "#r \"bin/Debug/net6.0/Nuqleon.Time.dll\""
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Option 2 - Use NuGet packages\n",
        "\n",
        "If you want to use the latest published package from NuGet, run the following cell."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "#r \"nuget:Nuqleon.Time,*-*\""
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## (Optional) Attach a debugger\n",
        "\n",
        "If you'd like to step through the source code of the library while running samples, run the following cell, and follow instructions to start a debugger (e.g. Visual Studio). Navigate to the source code of the library to set breakpoints."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "System.Diagnostics.Debugger.Launch();"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Stopwatches and clocks\n",
        "\n",
        "This library provides abstractions over stopwatches and clocks:\n",
        "\n",
        "* Stopwatches measure elapsed (relative) time.\n",
        "* Clocks provide absolute time.\n",
        "\n",
        "In the sample below, we show the use of an `IClock` implementation using virtual time in order to build an `IStopwatch`."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "First, let's create a `VirtualTimeClock` that starts at an initial time of `42` ticks."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "using System.Time;\n",
        "\n",
        "var virtTime = new VirtualTimeClock(initialTime: 42L);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Next, we go ahead and create an `IStopwatchFactory` that can be used to create `IStopwatch` instances which are a generalization of `System.Diagnostics.Stopwatch`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "IClock clock = virtTime;\n",
        "IStopwatchFactory factory = StopwatchFactory.FromClock(clock);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Given the factory, we can create new `IStopwatch` instances in ways similar to using `new Stopwatch()` or `Stopwatch.StartNew()`. In this case, we use the latter approach and observe that the stopwatch did not record any elapsed time."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "0\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "IStopwatch sw = factory.StartNew();\n",
        "\n",
        "Console.WriteLine(sw.ElapsedTicks);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "One we advance the virtual time clock, the stopwatch reflects the time elapsed."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "1\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "virtTime.Now = 43L;\n",
        "\n",
        "Console.WriteLine(sw.ElapsedTicks);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "If we `Stop` the stopwatch, elapsed time does no longer accumulate."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "1\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "sw.Stop();\n",
        "\n",
        "virtTime.Now = 45L;\n",
        "\n",
        "Console.WriteLine(sw.ElapsedTicks);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Once we resume the stopwatch using `Start`, elapsed time accumulates again."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "4\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "sw.Start();\n",
        "\n",
        "virtTime.Now = 48L;\n",
        "\n",
        "Console.WriteLine(sw.ElapsedTicks);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Finally, we can also create `IStopwatch` instances that are backed by `System.Diagnostics.Stopwatch`, as shown below."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "639\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "IStopwatch sw = StopwatchFactory.Diagnostics.StartNew();\n",
        "\n",
        "Console.WriteLine(sw.ElapsedTicks);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Using the abstractions provided in this library, one can parameterize libraries on notions of time, while keeping them testable. For example, `Nuqleon.Memory` uses `IStopwatch[Factory]` instances to measure elapsed time for cache operations. Testing of this library is deterministic by using stopwatches based on virtual clocks."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Clocks also provide a set of extension methods that help with ensuring monotonicity. An example of building a custom `IClock` that uses `Environment.TickCount` is shown below."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "class SystemClock : IClock\n",
        "{\n",
        "    public long Now => Environment.TickCount;\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Because `TickCount` is a 32-bit integer, the `TickCount` value will overflow after 24.9 days. This will cause our clock to be non-monotonic after running for this duration, which is problematic when calculating relative times, as done by e.g. stopwatches. We can guard against this using the `AssertMonotonic` extension method which will cause an exception to be thrown when the clock goes backwards, rather than producing invalid results."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "1072192265\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": "1072193265\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "using System.Threading;\n",
        "\n",
        "IClock systemClock = new SystemClock();\n",
        "IClock monotonicSystemClock = systemClock.AssertMonotonic();\n",
        "\n",
        "Console.WriteLine(monotonicSystemClock.Now);\n",
        "\n",
        "Thread.Sleep(1000); // If the clock would overflow over here, the following would throw.\n",
        "\n",
        "Console.WriteLine(monotonicSystemClock.Now);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Obviously, the issue above could be avoided by using `TickCount64` instead. Nonetheless, facilities to assert or ensure the required invariants of `IClock` instances are provided in this library to safeguard against these types of issues."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Creative use cases\n",
        "\n",
        "Clocks do not have to represent time and stopwatches do not have to represent accumulated time difference. Any use case where the result of subtracting `long` values yields a meaningful difference, and adding such differences together yields a meaningful accumulated amount is valid. A good example is the number of bytes allocated on a thread, as shown below."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "1691144\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "class MemoryClock : IClock\n",
        "{\n",
        "    public long Now => GC.GetAllocatedBytesForCurrentThread();\n",
        "}\n",
        "\n",
        "var mem = new MemoryClock();\n",
        "Console.WriteLine(mem.Now);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Given this definition of a \"clock\", we can build \"stopwatches\" to measure the amount of bytes allocated while the stopwatch is running."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "var factory = StopwatchFactory.FromClock(mem);\n",
        "\n",
        "var swAll = factory.Create();\n",
        "var sw1 = factory.Create();\n",
        "var sw2 = factory.Create();\n",
        "var swPrint = factory.Create();"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Now, let's measure the amount of memory allocated by a few operations, using the different stopwatches."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "Bytes allocated by F() = 640\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": "Bytes allocated by G() = 144\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": "Bytes allocated to print = 8488\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": "Total bytes allocated = 9272\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "using System.Runtime.CompilerServices;\n",
        "\n",
        "// Some functions to measure allocations for.\n",
        "[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]\n",
        "void F() => Enumerable.Range(0, 10).Select(x => x.ToString()).ToArray();\n",
        "\n",
        "[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]\n",
        "void G() => string.Join(\", \", new[] { \"bar\", \"foo\" });\n",
        "\n",
        "swAll.Start();\n",
        "{\n",
        "    sw1.Start();\n",
        "    {\n",
        "        F();\n",
        "    }\n",
        "    sw1.Stop();\n",
        "\n",
        "    swPrint.Start();\n",
        "    {\n",
        "        Console.WriteLine($\"Bytes allocated by F() = {sw1.ElapsedTicks}\");\n",
        "    }\n",
        "    swPrint.Stop();\n",
        "\n",
        "    sw2.Start();\n",
        "    {\n",
        "        G();\n",
        "    }\n",
        "    sw2.Stop();\n",
        "\n",
        "    swPrint.Start();\n",
        "    {\n",
        "        Console.WriteLine($\"Bytes allocated by G() = {sw2.ElapsedTicks}\");\n",
        "    }\n",
        "    swPrint.Stop();\n",
        "}\n",
        "swAll.Stop();\n",
        "\n",
        "Console.WriteLine($\"Bytes allocated to print = {swPrint.ElapsedTicks}\");\n",
        "Console.WriteLine($\"Total bytes allocated = {swAll.ElapsedTicks}\");"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Note that the sum of all allocations measured by `sw1`, `sw2`, and `swPrint` corresponds to the allocations reported by `swAll`.\n",
        "\n",
        "Using custom stopwatch types, one can also build composite stopwatches that combine multiple underlying stopwatches. For example, an allocation rate could be obtained by dividing bytes allocates by time elapsed. This library does not provide an algebra on top of the `IStopwatch[Factory]` family of interfaces, but it'd be feasible to build using the core abstractions."
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": ".NET (C#)",
      "language": "C#",
      "name": ".net-csharp"
    },
    "language_info": {
      "file_extension": ".cs",
      "mimetype": "text/x-csharp",
      "name": "C#",
      "pygments_lexer": "csharp",
      "version": "9.0"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 4
}